﻿using System;
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using HP.Cachalote.DataModels;
using HP.Cachalote.Services.Interfaces;
using HP.Cachalote.Services.Shared;
using HP.Cachalote.WPF.ExtendedAttachedProperties;
using HP.Cachalote.WPF.Model;
using HP.Cachalote.WPF.Shared;
using HP.Cachalote.Services;

namespace HP.Cachalote.WPF.ViewModel
{
    public class TodoItemTreeViewItemViewModel : MyViewModelBase, ITextBoxController
    {
        #region Members of ITextBoxController
        public event TextBoxControllerEventHandler SetFocus;
        public event TextBoxControllerEventHandler MoveFocusToNext;
        public event TextBoxControllerEventHandler MoveCursorToBeginning;
        public bool SetFocusInstantly { get; set; }
        #endregion

        public const string RequestDeleteItemMessageToken = "RequestDeleteItem";
        public const string RequestSetItemAsDoneMessageToken = "RequestSetItemAsDone";
        public const string RequestSetItemAsUndoneMessageToken = "RequestSetItemAsUndone";
        public const string RequestDisplayEditNoteWindowMessageToken = "RequestDisplayEditNoteWindowMessage";
        public const string RequestDisplayDueDateTimePickerWindowMessageToken = "RequestDisplayDueDateTimePickerWindowMessage";

        public int TodoItemID { get; set; }
        public string TodoLabelName { get; set; }
        public bool ShowLabelName { get; set; }

        private string _preChangedTitle;
        private ITodoItemService _todoItemService;

        /// <summary>
        /// Initializes a new instance of the UnfinishedTodoItemTreeViewItemViewModel class.
        /// </summary>
        /// <param name="dataService">An instance of IDataService that used to get ServiceFactory.</param>
        public TodoItemTreeViewItemViewModel(IDataService dataService)
            : base(dataService)
        {
            _todoItemService = ServiceFactory.CreateTodoItemService();
        }

        /// <summary>
        /// Register all necessary messages. It is called by constructor in base class.
        /// </summary>
        protected override void RegisterMessages()
        {
            base.RegisterMessages();

            RegisterNoteUpdatedMessage();
            RegisterDueDateChangedMessage();
            RegisterRequestDisplayDueDateTimePickerMessage();
        }

        /// <summary>
        /// The <see cref="Title" /> property's name.
        /// </summary>
        public const string TitlePropertyName = "Title";

        private string _title = string.Empty;

        /// <summary>
        /// Sets and gets the Title property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string Title
        {
            get
            {
                return _title;
            }

            set
            {
                if (_title == value)
                {
                    return;
                }

                RaisePropertyChanging(TitlePropertyName);
                _title = value;
                RaisePropertyChanged(TitlePropertyName);

                if (EditNoteViewModel != null)
                {
                    EditNoteViewModel.TodoItemTitle = Title;
                }

                if (DueTimePickerViewModel != null)
                {
                    DueTimePickerViewModel.TodoItemTitle = Title;
                }
            }
        }

        /// <summary>
        /// The <see cref="Note" /> property's name.
        /// </summary>
        public const string NotePropertyName = "Note";

        private string _note = string.Empty;

        /// <summary>
        /// Sets and gets the Note property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string Note
        {
            get
            {
                return _note;
            }

            set
            {
                if (_note == value)
                {
                    return;
                }

                RaisePropertyChanging(NotePropertyName);
                _note = value;
                RaisePropertyChanged(NotePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="Priority" /> property's name.
        /// </summary>
        public const string PriorityPropertyName = "Priority";

        private int _priority;

        /// <summary>
        /// Sets and gets the Priority property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public int Priority
        {
            get
            {
                return _priority;
            }

            set
            {
                if (_priority == value)
                {
                    return;
                }

                RaisePropertyChanging(PriorityPropertyName);
                _priority = value;
                RaisePropertyChanged(PriorityPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="DueDate" /> property's name.
        /// </summary>
        public const string DueDatePropertyName = "DueDate";

        private DateTime? _dueDate;

        /// <summary>
        /// Sets and gets the DueDate property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DateTime? DueDate
        {
            get
            {
                return _dueDate;
            }

            set
            {
                if (_dueDate == value)
                {
                    return;
                }

                RaisePropertyChanging(DueDatePropertyName);
                _dueDate = value;
                RaisePropertyChanged(DueDatePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="StartTime" /> property's name.
        /// </summary>
        public const string StartTimePropertyName = "StartTime";

        private DateTime? _startTime;

        /// <summary>
        /// Sets and gets the StartTime property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DateTime? StartTime
        {
            get
            {
                return _startTime;
            }

            set
            {
                if (_startTime == value)
                {
                    return;
                }

                RaisePropertyChanging(StartTimePropertyName);
                _startTime = value;
                RaisePropertyChanged(StartTimePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="EndTime" /> property's name.
        /// </summary>
        public const string EndTimePropertyName = "EndTime";

        private DateTime? _endTime;

        /// <summary>
        /// Sets and gets the EndTime property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DateTime? EndTime
        {
            get
            {
                return _endTime;
            }

            set
            {
                if (_endTime == value)
                {
                    return;
                }

                RaisePropertyChanging(EndTimePropertyName);
                _endTime = value;
                RaisePropertyChanged(EndTimePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="IsMatched" /> property's name.
        /// </summary>
        public const string IsMatchedPropertyName = "IsMatched";

        private bool _isMatched = true;

        /// <summary>
        /// Sets and gets the IsMatched property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool IsMatched
        {
            get
            {
                return _isMatched;
            }

            set
            {
                if (_isMatched == value)
                {
                    return;
                }

                RaisePropertyChanging(IsMatchedPropertyName);
                _isMatched = value;
                RaisePropertyChanged(IsMatchedPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="DueTimeDisplayViewModel" /> property's name.
        /// </summary>
        public const string DueTimeDisplayViewModelPropertyName = "DueTimeDisplayViewModel";

        private DueTimeDisplayViewModel _dueTimeDisplayViewModel;

        /// <summary>
        /// Sets and gets the DueTimeDisplayViewModel property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DueTimeDisplayViewModel DueTimeDisplayViewModel
        {
            get
            {
                return _dueTimeDisplayViewModel;
            }

            set
            {
                if (_dueTimeDisplayViewModel == value)
                {
                    return;
                }

                RaisePropertyChanging(DueTimeDisplayViewModelPropertyName);
                _dueTimeDisplayViewModel = value;
                RaisePropertyChanged(DueTimeDisplayViewModelPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="DueTimePickerViewModel" /> property's name.
        /// </summary>
        public const string DueTimePickerViewModelPropertyName = "DueTimePickerViewModel";

        private DueTimePickerViewModel _dueTimePickerViewModel;

        /// <summary>
        /// Sets and gets the DueTimePickerViewModel property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DueTimePickerViewModel DueTimePickerViewModel
        {
            get
            {
                return _dueTimePickerViewModel;
            }

            set
            {
                if (_dueTimePickerViewModel == value)
                {
                    return;
                }

                RaisePropertyChanging(DueTimePickerViewModelPropertyName);
                _dueTimePickerViewModel = value;
                RaisePropertyChanged(DueTimePickerViewModelPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="EditNoteViewModel" /> property's name.
        /// </summary>
        public const string EditNoteViewModelPropertyName = "EditNoteViewModel";

        private EditNoteViewModel _editNoteViewModel;

        /// <summary>
        /// Sets and gets the EditNoteViewModel property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public EditNoteViewModel EditNoteViewModel
        {
            get
            {
                return _editNoteViewModel;
            }

            set
            {
                if (_editNoteViewModel == value)
                {
                    return;
                }

                RaisePropertyChanging(EditNoteViewModelPropertyName);
                _editNoteViewModel = value;
                RaisePropertyChanged(EditNoteViewModelPropertyName);
            }
        }

        private RelayCommand _showEditNoteControlCommand;

        /// <summary>
        /// Gets the ShowEditNoteControlCommand.
        /// </summary>
        public RelayCommand ShowEditNoteControlCommand
        {
            get
            {
                return _showEditNoteControlCommand
                    ?? (_showEditNoteControlCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send message to MainWindow to show EditNoteWindow.
                                              Messenger.Default.Send<EditNoteViewModel>(EditNoteViewModel, RequestDisplayEditNoteWindowMessageToken);
                                          }));
            }
        }

        private RelayCommand _setToVeryHighPriorityCommand;

        /// <summary>
        /// Gets the SetToVeryHighPriorityCommand.
        /// </summary>
        public RelayCommand SetToVeryHighPriorityCommand
        {
            get
            {
                return _setToVeryHighPriorityCommand
                    ?? (_setToVeryHighPriorityCommand = new RelayCommand(
                                          () =>
                                          {
                                              try
                                              {
                                                  SetPriority(TodoItemPriority.VeryHigh);
                                              }
                                              catch (Exception ex)
                                              {
                                                  RequestDisplayErrorMessage(ex.Message);
                                              }
                                          }));
            }
        }

        private RelayCommand _setToHighPriorityCommand;

        /// <summary>
        /// Gets the SetToHighPriorityCommand.
        /// </summary>
        public RelayCommand SetToHighPriorityCommand
        {
            get
            {
                return _setToHighPriorityCommand
                    ?? (_setToHighPriorityCommand = new RelayCommand(
                                          () =>
                                          {
                                              try
                                              {
                                                  SetPriority(TodoItemPriority.High);
                                              }
                                              catch (Exception ex)
                                              {
                                                  RequestDisplayErrorMessage(ex.Message);
                                              }
                                          }));
            }
        }

        private RelayCommand _setToMediumPriorityCommand;

        /// <summary>
        /// Gets the SetToMediumPriorityCommand.
        /// </summary>
        public RelayCommand SetToMediumPriorityCommand
        {
            get
            {
                return _setToMediumPriorityCommand
                    ?? (_setToMediumPriorityCommand = new RelayCommand(
                                          () =>
                                          {
                                              try
                                              {
                                                  SetPriority(TodoItemPriority.Medium);
                                              }
                                              catch (Exception ex)
                                              {
                                                  RequestDisplayErrorMessage(ex.Message);
                                              }
                                          }));
            }
        }

        private RelayCommand _setToLowPriorityCommand;

        /// <summary>
        /// Gets the SetToLowPriorityCommand.
        /// </summary>
        public RelayCommand SetToLowPriorityCommand
        {
            get
            {
                return _setToLowPriorityCommand
                    ?? (_setToLowPriorityCommand = new RelayCommand(
                                          () =>
                                          {
                                              try
                                              {
                                                  SetPriority(TodoItemPriority.Low);
                                              }
                                              catch (Exception ex)
                                              {
                                                  RequestDisplayErrorMessage(ex.Message);
                                              }
                                          }));
            }
        }

        private RelayCommand _textBoxTitleGotFocusCommand;

        /// <summary>
        /// Gets the TextBoxTitleGotFocusCommand.
        /// </summary>
        public RelayCommand TextBoxTitleGotFocusCommand
        {
            get
            {
                return _textBoxTitleGotFocusCommand
                    ?? (_textBoxTitleGotFocusCommand = new RelayCommand(
                                          () =>
                                          {
                                              _preChangedTitle = Title;
                                          }));
            }
        }

        private RelayCommand<KeyEventArgs> _textBoxTitleOnKeyDownCommand;

        /// <summary>
        /// Gets the TextBoxTitleOnKeyDownCommand.
        /// </summary>
        public RelayCommand<KeyEventArgs> TextBoxTitleOnKeyDownCommand
        {
            get
            {
                return _textBoxTitleOnKeyDownCommand
                    ?? (_textBoxTitleOnKeyDownCommand = new RelayCommand<KeyEventArgs>(ExecuteTextBoxTitleOnKeyDownCommand));
            }
        }

        private void ExecuteTextBoxTitleOnKeyDownCommand(KeyEventArgs e)
        {
            try
            {
                if (e.Key == Key.Return)
                {
                    // This will trigger LostFocus command and save the change.
                    if (string.IsNullOrEmpty(Title))
                    {
                        Title = _preChangedTitle;
                    }

                    MoveFocusToNext(this);
                }
                else if (e.Key == Key.Escape)
                {
                    // If user presses Escape key then discard the change.
                    Title = _preChangedTitle;
                    MoveFocusToNext(this);
                }
            }
            catch (System.Exception ex)
            {
                RequestDisplayErrorMessage(ex.Message);
            }
        }

        private RelayCommand _textBoxTitleLostFocusCommand;

        /// <summary>
        /// Gets the TextBoxTitleLostFocusCommand.
        /// </summary>
        public RelayCommand TextBoxTitleLostFocusCommand
        {
            get
            {
                return _textBoxTitleLostFocusCommand
                    ?? (_textBoxTitleLostFocusCommand = new RelayCommand(ExecuteTextBoxTitleLostFocusCommand));
            }
        }

        private void ExecuteTextBoxTitleLostFocusCommand()
        {
            try
            {
                TryUpdateTodoItemTitle();
                MoveCursorToBeginning(this);
            }
            catch (System.Exception ex)
            {
                RequestDisplayErrorMessage(ex.Message);
            }
        }

        private RelayCommand _deleteCommand;

        /// <summary>
        /// Gets the DeleteCommand.
        /// </summary>
        public RelayCommand DeleteCommand
        {
            get
            {
                return _deleteCommand
                    ?? (_deleteCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send message to UnfinishedTodoItemTreeViewModel and show a Dialog to user to confirm deletion.
                                              Messenger.Default.Send<int>(TodoItemID, RequestDeleteItemMessageToken);
                                          }));
            }
        }

        private RelayCommand _doneCommand;

        /// <summary>
        /// Gets the DoneCommand.
        /// </summary>
        public RelayCommand DoneCommand
        {
            get
            {
                return _doneCommand
                    ?? (_doneCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send message to UnfinishedTodoItemTreeViewModel & FinishedTodoItemTreeViewModel 
                                              // to set a item as Done.
                                              var item = _todoItemService.Get(TodoItemID, true);

                                              Messenger.Default.Send<TodoItem>(item, RequestSetItemAsDoneMessageToken);
                                          }));
            }
        }

        private RelayCommand _undoneCommand;

        /// <summary>
        /// Gets the UndoneCommand.
        /// </summary>
        public RelayCommand UndoneCommand
        {
            get
            {
                return _undoneCommand
                    ?? (_undoneCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send message to UnfinishedTodoItemTreeViewModel & FinishedTodoItemTreeViewModel 
                                              // to set a item as Undoe.

                                              var item = _todoItemService.Get(TodoItemID, true);

                                              Messenger.Default.Send<TodoItem>(item, RequestSetItemAsUndoneMessageToken);
                                          }));
            }
        }

        /// <summary>
        /// Register NoteUpdatedMessage to change the Note once it is updated.
        /// </summary>
        private void RegisterNoteUpdatedMessage()
        {
            Messenger.Default.Register<EditNoteViewModel>(this, EditNoteViewModel.NoteUpdatedMessageToken, editNoteViewModel =>
            {
                if (TodoItemID == editNoteViewModel.TodoItemID)
                {
                    Note = editNoteViewModel.Note;
                }
            });
        }

        /// <summary>
        /// Get the DueDateChangedMessage from DueTimeDisplayViewModel to change the DueDateStatus once it is updated.
        /// </summary>
        private void RegisterDueDateChangedMessage()
        {
            Messenger.Default.Register<DueTimeDisplayViewModel>(this, DueTimeDisplayViewModel.DueDateChangedMessageToken, dueTimeDisplayViewModel =>
            {
                if (dueTimeDisplayViewModel.TodoItemID == TodoItemID)
                {
                    DueDate = dueTimeDisplayViewModel.DueDate;
                    StartTime = dueTimeDisplayViewModel.StartTime;
                    EndTime = dueTimeDisplayViewModel.EndTime;
                    DueTimeDisplayViewModel.DueDateStatus = InformationFormatter.GetDueDateStatus(DueDate, StartTime, EndTime);
                }
            });
        }

        /// <summary>
        /// Get RequestDisplayDueDateTimePickerMessage from DueTimeDisplayViewModel then send RequestDisplayDueDateTimePickerWindowMessage 
        /// to MainWindow. MainWindow will open DueDateTimeWindow on the view.
        /// </summary>
        private void RegisterRequestDisplayDueDateTimePickerMessage()
        {
            Messenger.Default.Register<int>(this, DueTimeDisplayViewModel.RequestDisplayDueDateTimePickerMessageToken, todoItemID =>
                {
                    if (todoItemID == TodoItemID)
                    {
                        // Send message to MainWindow to show DueDateTimePickerWindow.
                        Messenger.Default.Send<DueTimePickerViewModel>(DueTimePickerViewModel, RequestDisplayDueDateTimePickerWindowMessageToken);
                    }
                });
        }

        /// <summary>
        /// Try to update current TodoItem's title if it is changed.
        /// </summary>
        private void TryUpdateTodoItemTitle()
        {
            if (Title != _preChangedTitle)
            {
                var item = _todoItemService.Get(TodoItemID);
                item.Title = Title;
                item.UpdateOn = GeneralUtilities.GetCurrentDateTime();

                _todoItemService.Update(item);
            }
        }

        /// <summary>
        /// Update current TodoItem's priority.
        /// </summary>
        /// <param name="priotiry">New priority.</param>
        private void SetPriority(TodoItemPriority priotiry)
        {
            if ((int)priotiry != Priority)
            {
                Priority = (int)priotiry;

                var item = _todoItemService.Get(TodoItemID);
                item.Priority = (int)priotiry;
                item.UpdateOn = GeneralUtilities.GetCurrentDateTime();

                _todoItemService.Update(item);
            }
        }
    }
}
